﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Arch.Data;
using Arch.Common.Reflection;
using System.Reflection;
using Arch.Aspects;

namespace Arch.Intercep.Interceptors
{
    public class AspectInterceptor : IRepositoryInterceptor, IEntityInterceptor
    {
        public static IDictionary<Type, List<Tuple<ITargetInterceptor, MemberInfo>>> Interceptors =
            new Dictionary<Type, List<Tuple<ITargetInterceptor, MemberInfo>>>();

        public object Intercept(object entity, ActionTarget target)
        {
            var type = entity.GetType();

            //Note: We should be in presence of a proxy type, so we use the base type
            //TODO: create a better detection of proxy types
            if (type.Module.Assembly.IsDynamic)
            {
                type = type.BaseType;
            }

            CheckAspects(type);
            object currentEntity = entity;

            foreach (var aspect in Interceptors[type].Where(a => a.Item1.Targets.HasFlag(target)))
            {
                if (aspect.Item1 is IEntityInterceptor)
                    currentEntity = (aspect.Item1 as IEntityInterceptor).Intercept(currentEntity, target);
                if (aspect.Item1 is IMemberAspectInterceptor)
                    (aspect.Item1 as IMemberAspectInterceptor).Intercept(currentEntity, aspect.Item2);
            }

            return currentEntity;
        }

        public void Intercept<T>(IRepository<T> repository, ActionTarget target) where T : class, new()
        {
            var type = typeof(T);
            CheckAspects(type);

            foreach (var aspect in Interceptors[type].Where(a => a.Item1.Targets.HasFlag(target)))
            {
                if (aspect.Item1 is IRepositoryInterceptor)
                    (aspect.Item1 as IRepositoryInterceptor).Intercept(repository, target);

                if (aspect.Item1 is IRepositoryPropertyInterceptor)
                {
                    var interceptor = (aspect.Item1 as IRepositoryPropertyInterceptor);
                    foreach (var property in interceptor.Properties)
                    {
                        repository.set(property, interceptor.get(property));
                    }
                }
            }
        }

        protected virtual void CheckAspects(Type entityType)
        {
            if (!Interceptors.ContainsKey(entityType))
                lock (Interceptors)
                    if (!Interceptors.ContainsKey(entityType))
                        lock (Interceptors)
                        {
                            var aspects = GetAspects(entityType);

                            var methodInterceptors = (from aspect in aspects.Where(e => e.Item1 is ITargetInterceptor)
                                                      let interceptor = aspect.Item1 as ITargetInterceptor
                                                      select new Tuple<ITargetInterceptor, MemberInfo>(interceptor, aspect.Item2)).ToList();
                            Interceptors.Add(entityType, methodInterceptors);
                        }
        }

        protected virtual IEnumerable<Tuple<IAspectInterceptor, MemberInfo>> GetAspects(Type entityType)
        {
            Type aspectType = typeof(IAspectInterceptor);
            var aspects = new List<Tuple<IAspectInterceptor, MemberInfo>>();

            aspects.AddRange(entityType.GetCustomAttributes(aspectType, true)
                    .Select(aspect => new Tuple<IAspectInterceptor, MemberInfo>(aspect as IAspectInterceptor, entityType)));

            aspects.AddRange(from property in entityType.GetProperties()
                             from aspect in property.GetCustomAttributes(aspectType, true)
                             select new Tuple<IAspectInterceptor, MemberInfo>(aspect as IAspectInterceptor, property));

            aspects.AddRange(from method in entityType.GetMethods()
                             from aspect in method.GetCustomAttributes(aspectType, true)
                             select new Tuple<IAspectInterceptor, MemberInfo>(aspect as IAspectInterceptor, method));

            return aspects;
        }
    }
}
